#include "WupperConfig.h"
#include "wuppercard/WupperException.h"
//#include "EmuDataGenerator.h"
//#include "crc.h"
#include <vector>
#include <iostream>
#include <iomanip>
#include <sstream> // for ostringstream
#include <fstream> // for ifstream
using namespace std;

//#include "nlohmann/json.hpp"
//using namespace nlohmann;
// NOTE: Ignore the deprecated-declarations warning of gcc13, see WUPPER-2310
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
//#include "yaml-cpp/yaml.h"
#pragma GCC diagnostic pop

// ----------------------------------------------------------------------------

WupperConfig::WupperConfig( int device_nr )
  : _wupper( 0 ),
    _devNr( device_nr ),
    _bar2( 0 ),
    _firmwareMode( 0xFF ),
    _numberOfChans( 12 ),
    _ecToHostIndex( 0 ),
    _ecFromHostIndex( 0 ),
    _icToHostIndex( 0 ),
    _icFromHostIndex( 0 ),
    _dmaIndexInvalidCount( 0 ),
    _emulatorChunkCount( 0 ),
    _emulatorChunkRate( 0.0 )
{
  // Open FELIX WUPPER-card
  _wupper = new WupperCard;
  try {
    uint32_t lockbits = LOCK_NONE;
    _wupper->card_open( _devNr, lockbits );
  }
  catch( WupperException &ex ) {
    if( !_errString.empty() ) _errString += "; ";
    _errString += "WupperCard open: ";
    _errString += ex.what();
    delete _wupper;
    _wupper = 0;
    return;
  }

  // Get a pointer to the BAR2 registers
  _bar2 = (wuppercard_bar2_regs_t *) _wupper->bar2Address();

  // Read the device e-link configuration
  this->readLinkConfig();
}

// ----------------------------------------------------------------------------

WupperConfig::~WupperConfig()
{
  // Close the WUPPER-device
  try {
    if( _wupper )
      _wupper->card_close();
  }
  catch( WupperException &ex ) {
    // Do something..?
  }
  delete _wupper;
}

// ----------------------------------------------------------------------------

std::string WupperConfig::errorString()
{
  if( _errString.empty() )
    return std::string( "" );

  std::ostringstream oss;
  oss << "WupperConfig@Dev" << _devNr << ": " << _errString;

  // Clear the error string
  _errString.clear();

  return oss.str();
}

// ----------------------------------------------------------------------------

bool WupperConfig::hasError()
{
  return !_errString.empty();
}

// ----------------------------------------------------------------------------

std::string WupperConfig::firmwareVersion()
{
  if( _wupper == 0 ) return std::string();
  return std::string("2.0.0"); 
}

// ----------------------------------------------------------------------------

void WupperConfig::setFanOutForDaq()
{
  if( _wupper == 0 ) return;

  
}

// ----------------------------------------------------------------------------

bool WupperConfig::fanOutIsLocked()
{
  if( _wupper == 0 ) return false;

  // Check for the FANOUT_LOCK bit(s)
  bool locked = false;

  return locked;
}

// ----------------------------------------------------------------------------

bool WupperConfig::isElinkEnabled( int link, int egroup, int epath,
                                int *elinkwidth, bool from_host )
{
  return true;
}

// ----------------------------------------------------------------------------

bool WupperConfig::isElinkEnabled( int elinknr, int *elinkwidth, bool from_host )
{
  // Translate the given e-link number to link, e-group and e-path numbers
  return this->isElinkEnabled( 0, 0, 0, elinkwidth, from_host );
}

// ----------------------------------------------------------------------------

int WupperConfig::elinkMode( int link, int egroup, int epath, bool from_host )
{
  return 0;
}

// ----------------------------------------------------------------------------

int WupperConfig::elinkMode( int elinknr, bool from_host )
{
  return 0;
}

// ----------------------------------------------------------------------------

bool WupperConfig::isLpgbt()
{
  return false;
}

// ----------------------------------------------------------------------------
#if REGMAP_VERSION < 0x500
// ----------------------------------------------------------------------------

bool WupperConfig::configure( const std::string &filename )
{
  // For RM5 only...
  return false;
}

// ----------------------------------------------------------------------------

bool WupperConfig::writeLinkConfig( const std::string &filename,
                                 bool write_it,
                                 int  emu_chunksize,
                                 bool emu_ewidth_dependent_size,
                                 bool emu_random_size,
                                 int  emu_idle_chars,
                                 bool emu_with_streamid,
                                 bool lsb_first,
                                 bool do_regsettings,
                                 int *elinks_th, int *eciclinks_th,
                                 int *elinks_fh, int *eciclinks_fh,
                                 int *link_mode, int *settings_cnt )
{
  return true;
}

// ----------------------------------------------------------------------------
#else
// ----------------------------------------------------------------------------

bool WupperConfig::configure( const std::string &filename )
{
  _wupper->configure( filename );
  return true;
}

// ----------------------------------------------------------------------------

// The YAML file keys of the {key,value}-pairs
static const char *KEY_FORMAT     = "Format";
static const char *KEY_LINKS      = "Links";
static const char *KEY_LINK       = "Link";
static const char *KEY_LINKMODE   = "LinkMode";
static const char *KEY_TTCCLOCK   = "SetTtcClock";
static const char *KEY_CHUNKMAX   = "LinkChunkMaxSize";
//static const char *KEY_EGROUPS    = "Egroups";    // .jelc backwards compat
//static const char *KEY_EGROUPS_X  = "TheEgroups"; // .jelc backwards compat
static const char *KEY_EGROUP     = "Egroup";
static const char *KEY_EGROUPS_TH = "EgroupsToHost";
static const char *KEY_WIDTH_TH   = "WidthToHost";
static const char *KEY_ENA_TH     = "EnableToHost";
static const char *KEY_MODE_TH    = "ModeToHost";
static const char *KEY_STREAMID   = "StreamId";
static const char *KEY_DMAINDICES = "DmaIndices";
static const char *KEY_EGROUPS_FH = "EgroupsFromHost";
static const char *KEY_WIDTH_FH   = "WidthFromHost";
static const char *KEY_ENA_FH     = "EnableFromHost";
static const char *KEY_MODE_FH    = "ModeFromHost";
static const char *KEY_TTCOPTION  = "TtcOption";
static const char *KEY_SETTINGS   = "RegisterSettings";
static const char *KEY_FIELDNAME  = "Name";
static const char *KEY_FIELDVAL   = "Value";

bool WupperConfig::writeLinkConfig( const std::string &filename,
                                 bool write_it,
                                 int  emu_chunksize,
                                 bool emu_ewidth_dependent_size,
                                 bool emu_random_size,
                                 int  emu_idle_chars,
                                 bool emu_with_streamid,
                                 bool lsb_first,
                                 bool do_regsettings,
                                 int *elinks_th, int *eciclinks_th,
                                 int *elinks_fh, int *eciclinks_fh,
                                 int *link_mode, int *settings_cnt )
{
  // Open file
  std::ifstream file( filename );
  if( !file.is_open() )
    {
      _errString = "Failed to open YELC file: ";
      _errString += filename;
      return false;
    }

  // File name extension
  std::string ext;
  size_t pos = filename.find_last_of( "." );
  if( pos != string::npos )
    ext = filename.substr( pos+1 );

  // Read file contents (ASCII)
  int32_t  gbt = 0;
  uint32_t egroup = 0;
  uint32_t width, enables, chunksizes, linkmode, streamid, dmaindices;
  uint64_t modes;
  uint32_t ttc_option;
  bool     ttc_clock;
  std::vector<regsetting_t> regsettings;
  if( settings_cnt )
    *settings_cnt = 0;
  if( ext == std::string("yelc") )
    {
      try {
        YAML::Node yaml = YAML::Load( file );
        if( !yaml[KEY_FORMAT] )
          {
            _errString += "YELC-file has wrong format (\"Format\" missing)";
            return false;
          }
        int format = yaml[KEY_FORMAT].as<int>();
        if( format != 2 )
          {
            _errString += "YELC-file: \"Format\" 2 required";
            return false;
          }

        YAML::Node links = yaml[KEY_LINKS];
        for( YAML::const_iterator it = links.begin(); it != links.end(); ++it )
          {
            YAML::Node lnk = *it;
            gbt = lnk[KEY_LINK].as<int>();
            if( gbt > WUPPER_LINKS ) // Shouldn't read in past this number!
              continue;
            if( gbt == -1 ) // Emulator configuration
              gbt = WUPPER_LINKS;

            chunksizes = lnk[KEY_CHUNKMAX].as<int>();
            _gbtConfig[gbt].setMaxChunkWord( chunksizes );

            linkmode = lnk[KEY_LINKMODE].as<int>();
            _gbtConfig[gbt].setLinkMode( linkmode );

            if( gbt == 0 )
              {
                if( lnk[KEY_TTCCLOCK] )
                  ttc_clock = lnk[KEY_TTCCLOCK].as<bool>();
                else
                  ttc_clock = false;
                _gbtConfig[gbt].setTtcClock( ttc_clock );
              }

            YAML::Node egrps = lnk[KEY_EGROUPS_TH];
            istringstream iss;
            for( YAML::const_iterator it2 = egrps.begin();
                 it2 != egrps.end(); ++it2 )
              {
                YAML::Node egrp = *it2;
                egroup = egrp[KEY_EGROUP].as<int>();

                // Convert to integers (some from a hexadecimal representation)
                width = 0, enables = 0; modes = 0;
                width = egrp[KEY_WIDTH_TH].as<int>();
                iss.clear();
                iss.str( egrp[KEY_ENA_TH].as<std::string>() );
                iss >> hex >> enables;
                iss.clear();
                iss.str( egrp[KEY_MODE_TH].as<std::string>() );
                iss >> hex >> modes;
                _gbtConfig[gbt].setWidthToHost( egroup, width );
                _gbtConfig[gbt].setEnablesToHost( egroup, enables );
                _gbtConfig[gbt].setModesToHost( egroup, modes );

                // Optional item (for backwards-compatibility)
                if( egrp[KEY_DMAINDICES] )
                  {
                    dmaindices = 0;
                    iss.clear();
                    iss.str( egrp[KEY_DMAINDICES].as<std::string>() );
                    iss >> hex >> dmaindices;
                    _gbtConfig[gbt].setDmaIndices( egroup, dmaindices );
                  }

                streamid = 0;
                iss.clear();
                iss.str( egrp[KEY_STREAMID].as<std::string>() );
                iss >> hex >> streamid;
                _gbtConfig[gbt].setStreamIdBits( egroup, streamid );
              }

            egrps = lnk[KEY_EGROUPS_FH];
            for( YAML::const_iterator it2 = egrps.begin();
                 it2 != egrps.end(); ++it2 )
              {
                YAML::Node egrp = *it2;
                egroup = egrp[KEY_EGROUP].as<int>();

                // Convert to integers (some from a hexadecimal representation)
                width = 0; enables = 0; modes = 0;
                width = egrp[KEY_WIDTH_FH].as<int>();
                iss.clear();
                iss.str( egrp[KEY_ENA_FH].as<std::string>() );
                iss >> hex >> enables;
                iss.clear();
                iss.str( egrp[KEY_MODE_FH].as<std::string>() );
                iss >> hex >> modes;
                if( egrp[KEY_TTCOPTION] )
                  {
                    ttc_option = egrp[KEY_TTCOPTION].as<int>();
                    _gbtConfig[gbt].setTtcOptionFromHost( egroup, ttc_option );
                  }
                _gbtConfig[gbt].setWidthFromHost( egroup, width );
                _gbtConfig[gbt].setEnablesFromHost( egroup, enables );
                _gbtConfig[gbt].setModesFromHost( egroup, modes );
              }
          }

        YAML::Node settings = yaml[KEY_SETTINGS];
        regsetting_t regset;
        for( YAML::const_iterator it = settings.begin();
             it != settings.end(); ++it )
          {
            YAML::Node setting = *it;
            std::string field = setting[KEY_FIELDNAME].as<std::string>();
            uint64_t value = setting[KEY_FIELDVAL].as<uint64_t>();
            regset.name = field;
            regset.value = value;
            regsettings.push_back( regset );
          }
        if( settings_cnt )
          *settings_cnt = regsettings.size();
      }
      catch( std::exception &ex ) {
        _errString += "Error while reading YAML-file:\n    ";
        _errString += ex.what();
        return false;
      }
    }
  else
    {
      _errString += "File format not supported: .";
      _errString += ext;
      return false;
    }
  file.close();

  if( _wupper == 0 ) return false;

  uint64_t chans = _bar2->NUM_OF_CHANNELS;
  if( chans > WUPPER_LINKS ) chans = WUPPER_LINKS;
  _numberOfChans = chans;

  // Check firmware/configuration compatibility
  uint32_t mode = _gbtConfig[0].linkMode();
  if( link_mode ) *link_mode = mode;
  if( mode == LINKMODE_FULL && !_wupper->fullmode_type() )
    {
      _errString = "E-link config FULLMODE: incompatible firmware!";
      if( write_it ) return false;
    }
  else if( (mode == LINKMODE_GBT || mode == LINKMODE_GBTWIDE) &&
           (_wupper->lpgbt_type() || _wupper->fullmode_type()) )
    {
      _errString = "E-link config GBT: incompatible firmware!";
      if( write_it ) return false;
    }
  else if( (mode == LINKMODE_LPGBT10_F5 || mode == LINKMODE_LPGBT5_F5) &&
           !_wupper->lpgbt_type() )
    {
      _errString = "E-link config lpGBT: incompatible firmware!";
      if( write_it ) return false;
    }

  // *************************************
  // All of the above (plus writeLinkConfig() and regsettings below,
  // but emulator configuration (still) missing)
  // now could be replaced by:
  /*
  try {
    _wupper->configure( filename, write_it, do_regsettings );
  }
  catch( WupperException &ex ) {
    _errString = ex.what();
    return false;
  }
  */
  // *************************************

  if( write_it )
    {
      // Configure requested 8b10b word bit order (to- and from-host direction)
      if( lsb_first )
        {
          _bar2->ENCODING_REVERSE_10B = 0;
          _bar2->DECODING_REVERSE_10B = 0;
        }
      else
        {
          _bar2->ENCODING_REVERSE_10B = 1;
          _bar2->DECODING_REVERSE_10B = 1;
        }

      // Link configuration
      this->writeLinkConfig();

      // Emulator configuration
      if( mode != LINKMODE_FULL )
        this->writeGbtEmulatorData( emu_chunksize, emu_ewidth_dependent_size,
                                    emu_random_size, emu_idle_chars,
                                    emu_with_streamid,
                                    lsb_first );
      else
        this->writeFmEmulatorData( emu_chunksize,
                                   emu_random_size, emu_idle_chars,
                                   emu_with_streamid );

      // And finally the additional register settings provided, if any
      if( do_regsettings )
        for( regsetting_t &r : regsettings )
          _wupper->cfg_set_option( r.name.c_str(), r.value );
    }

  if( elinks_th )
    {
      *elinks_th = 0;
      for( uint32_t gbt=0; gbt<chans; ++gbt )
        *elinks_th += _gbtConfig[gbt].numberOfElinksToHost();
    }
  if( eciclinks_th )
    {
      *eciclinks_th = 0;
      for( uint32_t gbt=0; gbt<chans; ++gbt )
        *eciclinks_th += _gbtConfig[gbt].numberOfEcIcToHost();
    }
  if( elinks_fh )
    {
      *elinks_fh = 0;
      for( uint32_t gbt=0; gbt<chans; ++gbt )
        *elinks_fh += _gbtConfig[gbt].numberOfElinksFromHost();
    }
  if( eciclinks_fh )
    {
      *eciclinks_fh = 0;
      for( uint32_t gbt=0; gbt<chans; ++gbt )
        *eciclinks_fh += _gbtConfig[gbt].numberOfEcIcFromHost();
    }

  return true;
}

// ----------------------------------------------------------------------------
#endif //REGMAP_VERSION
// ----------------------------------------------------------------------------
/*
int WupperConfig::firmwareMode()
{
  if( _wupper == 0 ) return 0xFF;
  return _bar2->FIRMWARE_MODE;
}

// ----------------------------------------------------------------------------

int WupperConfig::numberOfChans()
{
  if( _wupper == 0 ) return 0;
  return _bar2->NUM_OF_CHANNELS;
}
*/
// ----------------------------------------------------------------------------
// Private functions
// ----------------------------------------------------------------------------

void WupperConfig::readLinkConfig()
{
  if( _wupper == 0 ) return;

  
}

// ----------------------------------------------------------------------------

#define EGROUP_CTRL_MODE_SHIFT     15
#define EGROUP_CTRL_MAXCHUNK_SHIFT 31
#define EMU_CONFIG_ADDR_SHIFT      32

void WupperConfig::writeLinkConfig()
{
}

// ----------------------------------------------------------------------------

void WupperConfig::writeGbtEmulatorData( uint32_t req_chunksize,
                                      bool     width_prop_sz,
                                      bool     random_sz,
                                      uint32_t idle_chars,
                                      bool     use_streamid,
                                      bool     lsb_first )
{
  
}

// ----------------------------------------------------------------------------

void WupperConfig::writeFmEmulatorData( uint32_t req_chunksize,
                                     bool     random_sz,
                                     uint32_t idle_chars,
                                     bool     use_streamid )
{
  
}

// ----------------------------------------------------------------------------
